//
//  YXGlobalCaching.swift
//  
//
//  Created by tangbl93 on 2021/2/27.
//

import Foundation

// MARK: YXGlobalCaching
public final class YXGlobalCaching {
    
    // shared instance
    private static let shared = YXGlobalCaching()
    
    // enable logging
    public var enableLogging: Bool = true
    
    // caching services list, with builtin YXUDCaching
    private var cachingServices: [YXCaching] = [YXUDCaching()]
    
    // init
    init() {
        cachingLog("caching init")
    }

    // register caching service
    public func register<T: YXCaching>(caching service: T) {
        cachingLog("caching register service:\(service)")
        
        cachingServices.append(service)
    }
    
    // remove caching service
    public func remove<T: YXCaching>(caching service: T) {
        cachingLog("caching remove service:\(service)")
        
        fatalError()
    }
}

// MARK: Migrate
extension YXGlobalCaching {
    
    // migrate version
    private var version: Int? {
        get {
            return YXUDCaching.shared.value(forKey: Self.versionKey) as? Int
        }
        set {
            YXUDCaching.shared.set(newValue, forKey: Self.versionKey)
        }
    }
    private static let versionKey = "YXSilver.caching.version"
    
    // migrate caching version
    private func migrate(to version: Int, action: VoidClosure) {
        // only migrate to new version
        let current = self.version ?? 0
        guard version > current else { return }
        
        defer {
            self.version = version
            cachingLog("caching migrate to:\(version) done")
        }
        
        cachingLog("caching migrate to:\(version)")
        
        action()
    }
    
    /// Migrate process
    ///
    /// ```
    /// GlobalCaching.migrate { () -> [YXCachingMigrate] in
    ///        YXCachingMigrate(version: 1) {}
    ///        YXCachingMigrate(version: 2) {}
    ///        YXCachingMigrate(version: 3) {}
    ///    } completionHandler: {
    ///         ...
    ///    }
    /// }
    /// ```
    /// 
    /// - Parameters:
    ///   - builder: migrate list
    ///   - completion: completion handler
    public static func migrate(@YXCachingMigrateBuilder<YXCachingMigrate> builder: () -> [YXCachingMigrate], completionHandler completion: VoidClosure? = nil) {
        let migrations = builder()
        migrations.forEach { (migration) in
            shared.migrate(to: migration.version, action: migration.action)
        }
        completion?()
    }
}

// MARK: Logging
extension YXGlobalCaching {
    
    private func cachingLog(_ log: String) {
        if enableLogging { YXNSLog(log) }
    }
    
    private static func cachingLog(_ log: String) {
        shared.cachingLog(log)
    }
}

// MARK: YXCachingStatic
extension YXGlobalCaching: YXCachingStatic {
    
    public static func value(forKey key: String) -> Any? {
        cachingLog("caching value forKey:'\(key)'")
        
        var result: Any? = nil
        shared.cachingServices.forEach { (cachingService) in
            if let value = cachingService.value(forKey: key) {
                result = value
                return
            }
        }
        return result
    }
    
    public static func value(forKey key: String, default object: Any) -> Any {
        cachingLog("caching value forKey:'\(key)', default: \(object)")
        
        return value(forKey: key) ?? object
    }
    
    public static func set(_ value: Any?, forKey key: String) {
        cachingLog("caching setValue:\(String(describing: value)) forKey:'\(key)'")
        
        shared.cachingServices.forEach { (cachingService) in
            cachingService.set(value, forKey: key)
        }
    }
    
    public static func transition(from oldKey: String, to newKey: String) {
        cachingLog("caching transition from:'\(oldKey)' to:'\(newKey)'")
        
        shared.cachingServices.forEach { (cachingService) in
            cachingService.transition(from: oldKey, to: newKey)
        }
    }
    
    public static func removeAll() {
        cachingLog("caching removeAll")
        
        shared.cachingServices.forEach { (cachingService) in
            cachingService.removeAll()
        }
    }
    
    public static func removeObject(forKey key: String) {
        cachingLog("caching removeObject forKey:'\(key)'")
        
        shared.cachingServices.forEach { (cachingService) in
            cachingService.removeObject(forKey: key)
        }
    }
}

// MARK: YXCachingStatic convert
extension YXGlobalCaching {
    
    public static func string(forKey key: String) -> String? {
        cachingLog("caching string forKey:'\(key)'")
        
        return value(forKey: key) as? String
    }
    
    public static func integer(forKey key: String) -> Int {
        cachingLog("caching integer forKey:'\(key)'")
        
        return value(forKey: key) as? Int ?? 0
    }
    
    public static func float(forKey key: String) -> Float {
        cachingLog("caching float forKey:'\(key)'")
        
        return value(forKey: key) as? Float ?? 0
    }
    
    public static func double(forKey key: String) -> Double {
        cachingLog("caching double forKey:'\(key)'")
        
        return value(forKey: key) as? Double ?? 0
    }
    
    public static func bool(forKey key: String) -> Bool {
        cachingLog("caching bool forKey:'\(key)'")
        
        return value(forKey: key) as? Bool ?? false
    }
    
    public static func data(forKey key: String) -> Data? {
        cachingLog("caching data forKey:'\(key)'")
        
        return value(forKey: key) as? Data
    }
    
    public static func array(forKey key: String) -> [Any]? {
        cachingLog("caching array forKey:'\(key)'")
        
        return value(forKey: key) as? [Any]
    }

    public static func dictionary(forKey key: String) -> [String : Any]? {
        cachingLog("caching dictionary forKey:'\(key)'")
        
        return value(forKey: key) as? [String : Any]
    }

    public static func stringArray(forKey key: String) -> [String]? {
        cachingLog("caching stringArray forKey:'\(key)'")
        
        return value(forKey: key) as? [String]
    }
}
